#include "ReceiveWindow.h"
#include "stdGNUTDefs.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

ReceiveWindow::ReceiveWindow()
{

}

ReceiveWindow::ReceiveWindow(unsigned int wndwSize)
{
	myWindowSize = wndwSize;
	bytesWritten = 0;
	alive = true;
	tail = NULL;
	head = NULL;
}

/**
 * For this function we are given a valid packet to add
 * but this packet may be overlapping with other received
 * packets, may be out of range of the window, may be old,
 * etc...
 */
void ReceiveWindow::addPacket(Packet p)
{
	verbose ? printf("add packet to receiver window:\n"):1;
	verbose ? printf("SequNo: %d\n", p.getSeqNo()):1;
	verbose ? printf("datLen: %d\n", p.getPayloadLength()):1;

	// if the packet is a RST, then we should die and exit
	if( p.isRST() || p.isFIN() )
	{
		verbose ? printf("packet is RST or FIN, so kill window and exit\n"):1;
		leftWindowEdge = p.getSeqNo();
		alive = false;
		return;
	}

	// discard empty data packets... 
	if( p.isDAT() && p.getPayloadLength() == 0 )
	{
		verbose ? printf("empty data packet, discard\n"):1;
		return;
	}
	// if packet's data is out of bounds in either 
	// direction in the window, then clip it
	// note that we are not acking packets that are completely out of bounds
	// TODO: don't know if this is a good idea!
	if( !clipPacket(p) )
	{
		verbose ? printf("packet out of receiver window entirely\n"):1;
		return;
	}

	// At this stage we are assured that the packet lies within the receive window


	GNUT_dataBlock_t* forwardNode;
	int overlap;

	// check for collision
	if( detectCollision(p.getSeqNo(), &forwardNode) ) // we've collided with a current dataBlock
	{
		// check for total collision (packet is inside another dataBlock
		if( forwardNode->SeqNo + forwardNode->size - p.getSeqNo() < p.getPayloadLength() ) // then this packet not eclipsed
		{
			overlap = forwardNode->SeqNo + forwardNode->size - p.getSeqNo();
			p.setSeqNo( forwardNode->SeqNo + forwardNode->size );
			p.setPayload( p.getPayload() + overlap, p.getPayloadLength() - overlap );
			addPacket(p);
		} else // this packet is eclipsed by currently received data
			return;
	} else // sequence number doesn't lie in a current data block
	{
		// new code
		// if nobody in front of us, or if we don't run into the block in front of us
		if( (forwardNode == NULL) || !(forwardNode->SeqNo < p.getSeqNo() + p.getPayloadLength()))
		{
			GNUT_dataBlock_t* newDB = createDataBlock(p);
			addDataBlock(newDB);
			slide();
		} else
		{
			// there's someone in front of us and we run into them
			// let overlap be interpreted here as the overlapping free space w/ current packet
			overlap = forwardNode->SeqNo - p.getSeqNo();
			char* oldPayloadPtr = p.getPayload();
			unsigned int oldPayloadLen = p.getPayloadLength();
			unsigned int oldSeqNo = p.getSeqNo();
			p.setPayload(p.getPayload(), overlap);
			addPacket(p);
			p.setSeqNo(oldSeqNo + overlap);
			p.setPayload(oldPayloadPtr + overlap, oldPayloadLen - overlap);
			addPacket(p);
		}
	}

}

void ReceiveWindow::addDataBlock(GNUT_dataBlock_t* dataBlock)
{
	GNUT_dataBlock_t* dbPtr = tail;

	// if we have an empty window
	if( head == NULL )
	{
		tail = dataBlock;
		head = dataBlock;
		dataBlock->next = NULL;
		dataBlock->prev = NULL;
	}
	else
	{
		while( (dbPtr != NULL) && (dataBlock->SeqNo > dbPtr->SeqNo) )
			dbPtr = dbPtr->next;

		if( dbPtr == NULL ) // add to front of list 
		{
			head->next = dataBlock;
			dataBlock->prev = head;
			head = dataBlock;
			dataBlock->next = NULL;
		} else
		{
			if( tail != dbPtr )
			{
				dataBlock->next = dbPtr;
				dataBlock->prev = dbPtr->prev;
				dbPtr->prev = dataBlock;
				(dataBlock->prev)->next = dataBlock;
			} else
			{
				tail = dataBlock;
				dataBlock->next = dbPtr;
				dbPtr->prev = dataBlock;
				dataBlock->prev = NULL;
			}
		}
	}

}

GNUT_dataBlock_t* ReceiveWindow::createDataBlock(Packet p)
{
	char* datatemp = (char*)malloc(p.getPayloadLength()*sizeof(char));
	GNUT_dataBlock_t* dBtemp = (GNUT_dataBlock_t*)malloc(sizeof(GNUT_dataBlock_t)*sizeof(char));
	dBtemp->SeqNo = p.getSeqNo();
	dBtemp->size = p.getPayloadLength();
	dBtemp->dataPtr = datatemp;
	memcpy(dBtemp->dataPtr, p.getPayload(), p.getPayloadLength());

	return dBtemp;
}

/**
 * The job of this function is to look at the current linked list, and determine
 * what data can be processed (i.e. written to disk), and then slide the window
 * and update our state variables
 */
void ReceiveWindow::slide()
{
	//verbose ? printf("slide ze window\n"):1;
	//verbose ? printf("leftWindowEdge: %d\n", leftWindowEdge):1;

	while( (tail != NULL) && (leftWindowEdge + 1 == tail->SeqNo) )
	{

		// write dataBlock to disk
		// free dataBlock
		// update pointers so that tail points to the next block

		verbose ? printf("writing: %s\n", tail->dataPtr):1;
		if( write(myFd, tail->dataPtr, tail->size) == -1 )
			printf("error writing to file\n");
		bytesWritten += tail->size;

		leftWindowEdge = leftWindowEdge + tail->size;

		GNUT_dataBlock_t* tempPtr;
		tempPtr = tail;
		tail = tail->next;

		if( tail != NULL )
			tail->prev = NULL;
		else
			head = NULL;

		free(tempPtr->dataPtr);
		free(tempPtr);
	}
}

int ReceiveWindow::getBytesWritten()
{
	return bytesWritten;
}

bool ReceiveWindow::detectCollision(unsigned int packetSeqNo, GNUT_dataBlock_t** returnNode)
{
	//verbose ? printf("try to detect collisions\n"):1;

	GNUT_dataBlock_t* curNode = tail;

	while( curNode != NULL )
	{
		if( curNode->SeqNo > packetSeqNo )
		{
			*returnNode = curNode;
			return false;
		}

		if( packetSeqNo < curNode->SeqNo + curNode->size )
		{
			*returnNode = curNode;
			return true;
		}

		curNode = curNode->next;
	}

	*returnNode = curNode;
	return false;
}

/**
 * clip the packet at the left and right edge of our
 * receiver window. If there's data left after clipping,
 * clipPacket returns 1, else it returns 0 to indicate
 * that the packet was completely out of bounds
 */
int ReceiveWindow::clipPacket(Packet &p)
{
	//verbose ? printf("clip the packet\n"):1;
	int overlap;

	// check lower bound clipping
	if( p.getSeqNo() <= leftWindowEdge ) // then we have lower bound clipping
	{
		if( p.getSeqNo() + p.getPayloadLength() - 1 > leftWindowEdge ) // check to see if any of the packet is within the window
		{
			overlap = leftWindowEdge - p.getSeqNo() + 1;
			p.setSeqNo(p.getSeqNo() + overlap);
			p.setPayload(p.getPayload() + overlap, p.getPayloadLength() - overlap);
		} else
			return 0;
	}

	// check upper bound clipping
	if( p.getSeqNo() + p.getPayloadLength() - 1 > leftWindowEdge + myWindowSize ) // then upper clipping
	{
		if( p.getSeqNo() <= leftWindowEdge + myWindowSize ) // then there's something worth saving
		{
			overlap = p.getSeqNo() + p.getPayloadLength() - 1 - leftWindowEdge - myWindowSize;
			//verbose ? printf("overlap: %d\n", overlap):1;
			p.setPayload(p.getPayload(), p.getPayloadLength() - overlap);
			//verbose ? printf("payloadlen: %d\n", p.getPayloadLength()):1;
		} else // nothing worth saving!
			return 0;
	}

	return 1;	
}

void ReceiveWindow::setTargetFile(int fd)
{
	myFd = fd;
}

void ReceiveWindow::setISN(unsigned int ISN)
{
	leftWindowEdge = ISN;
}

bool ReceiveWindow::isAlive()
{
	return alive;
}

int ReceiveWindow::getLeftWindowEdge()
{
	return leftWindowEdge;
}

int ReceiveWindow::getRemainingWindowSize()
{
	if( head == NULL )
		return myWindowSize;

	return (leftWindowEdge + myWindowSize - (head->SeqNo + head->size - 1));	
}

int ReceiveWindow::printWindowState()
{
	GNUT_dataBlock_t* blockPtr;
	blockPtr = tail;

	printf("--------------Printing Window State--------------\n");


	printf("Range: %d->%d\n", leftWindowEdge + 1, leftWindowEdge + myWindowSize);
	printf("Contents: ");


	while( blockPtr != NULL )
	{
		printf("|%d, %d|--", blockPtr->SeqNo, blockPtr->SeqNo + blockPtr->size - 1);
		blockPtr = blockPtr->next;
	}

	printf("NULL\n");

	printf("-------------------------------------------------\n");

	return 1;
}

/**
 * Clears the window contents
 */
void ReceiveWindow::clear()
{
	head = NULL;

	while( tail != NULL )
	{
		GNUT_dataBlock_t* tempPtr;
		tempPtr = tail;
		tail = tail->next;

		free(tempPtr->dataPtr);
		free(tempPtr);
	}
	
	bytesWritten = 0;
}

ReceiveWindow::~ReceiveWindow()
{
}
